// --------------------------------------------------------------------------------------------------------------------
// <copyright file="State.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Microsoft.Robotics.FiniteStateMachine
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.Robotics.Tracing;

    /// <summary>
    /// States are the classic FSM states. They’re given a name (for logging/debugging)
    /// and have an (optional) OnEnter and OnExit function called as things change.
    /// </summary>
    /// <typeparam name="TContext">Context type</typeparam>
    public sealed class State<TContext>
    {
        /// <summary>
        /// Enter event handler.
        /// </summary>
        private readonly Func<TContext, TContext> enterFn;

        /// <summary>
        /// Exit event handler.
        /// </summary>
        private readonly Func<TContext, TContext> exitFn;

        /// <summary>
        /// State name (for debugging).
        /// </summary>
        private readonly string name;

        /// <summary>
        /// List of transitions.
        /// </summary>
        private List<Transition> transitions = new List<Transition>();

        /// <summary>
        /// Initializes a new instance of the State class.
        /// </summary>
        /// <param name="name">State name (for debugging).</param>
        /// <param name="enterFn">On enter callback function (optional).</param>
        /// <param name="exitFn">On exit callback function (optional).</param>
        public State(string name, Func<TContext, TContext> enterFn = null, Func<TContext, TContext> exitFn = null)
        {
            this.name = name;
            this.enterFn = enterFn;
            this.exitFn = exitFn;
        }

        /// <summary>
        /// String representation of state.
        /// </summary>
        /// <returns>String representation.</returns>
        public override string ToString()
        {
            return this.name;
        }

        /// <summary>
        /// Adds a transition to the state.
        /// </summary>
        /// <param name="condition">Function to evaluate condition.</param>
        /// <param name="to">Target state.</param>
        /// <param name="transitionFn">Function called on transition.</param>
        public void AddTransition(Func<TContext, bool> condition, State<TContext> to, Func<TContext, TContext> transitionFn = null)
        {
            this.transitions.Add(new Transition(condition, to, transitionFn));
        }

        /// <summary>
        /// Function to call whenever updates on state have occurred.
        /// </summary>
        /// <param name="context">Instance of context.</param>
        /// <param name="updated">Resulting updated context.</param>
        /// <returns>Resulting new state.</returns>
        public State<TContext> Updated(TContext context, out TContext updated)
        {
            var tran = (from t in this.transitions where t.Condition(context) select t).FirstOrDefault();
            if (tran == null)
            {
                // no change
                updated = context;
                return this;
            }

            if (tran.To != this)
            {
                TraceOut.Info(TraceContexts.Common, "State transition {0} -> {1} ({2:u})", this.name, tran.To.name, DateTime.Now);
            }

            updated = tran.Traverse(this.exitFn == null ? context : this.exitFn(context));
            return tran.To;
        }

        /// <summary>
        /// Transition class.
        /// </summary>
        private sealed class Transition
        {
            /// <summary>
            /// Function to evaluate transition condition.
            /// </summary>
            public readonly Func<TContext, bool> Condition;

            /// <summary>
            /// Target state of the transition.
            /// </summary>
            public readonly State<TContext> To;

            /// <summary>
            /// Function called to perform the transition.
            /// </summary>
            private readonly Func<TContext, TContext> transitionFn;

            /// <summary>
            /// Initializes a new instance of the Transition class.
            /// </summary>
            /// <param name="condition">Condition to fulfill to traverse the transition.</param>
            /// <param name="to">Target state.</param>
            /// <param name="transitionFn">Event handler (optional).</param>
            public Transition(Func<TContext, bool> condition, State<TContext> to, Func<TContext, TContext> transitionFn = null)
            {
                this.Condition = condition;
                this.To = to;
                this.transitionFn = transitionFn;
            }

            /// <summary>
            /// Traverses the transition.
            /// </summary>
            /// <param name="context">Context instance.</param>
            /// <returns>Resulting context.</returns>
            public TContext Traverse(TContext context)
            {
                TContext ctx = this.transitionFn == null ? context : this.transitionFn(context);
                return this.To.enterFn == null ? ctx : this.To.enterFn(ctx);
            }
        }
    }
}
